// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef NET_URL_REQUEST_URL_REQUEST_BACKOFF_MANAGER_H_
#define NET_URL_REQUEST_URL_REQUEST_BACKOFF_MANAGER_H_

#include <stdint.h>

#include <map>
#include <string>

#include "base/macros.h"
#include "base/threading/non_thread_safe.h"
#include "base/time/time.h"
#include "net/base/net_export.h"
#include "net/base/network_change_notifier.h"
#include "url/gurl.h"

namespace net {

class HttpResponseHeaders;

// Class that manages information on Backoff headers. URL requests for HTTPS
// contents should update their URLs in this manager on each response.
//
// Design doc:
// https://docs.google.com/document/d/1aAxwXK7Vw3VigFd6MmrItbAIgMdKAf-XxXXbhWXdID0/edit?usp=sharing
//
// URLRequestBackoffManager maintains a map of URL IDs to
// URLRequestBackoffManager::Entry. It creates an entry when a request receives
// a Backoff header, and does garbage collection from time to time in order to
// clean out outdated entries. URL ID consists of lowercased scheme, host, port
// and path. A newer request with the same ID will override the old entry.
//
// Note that the class does not implement logic to retry a request at random
// with uniform distribution.
// TODO(xunjieli): Expose release time so that the caller can retry accordingly.
class NET_EXPORT URLRequestBackoffManager
    : NON_EXPORTED_BASE(public base::NonThreadSafe),
      public NetworkChangeNotifier::IPAddressObserver,
      public NetworkChangeNotifier::ConnectionTypeObserver {
public:
    // Minimum number of seconds that a Backoff header can specify.
    static const uint16_t kMinimumBackoffInSeconds;
    // Maximum number of seconds that a Backoff header can specify.
    static const uint16_t kMaximumBackoffInSeconds;
    // Number of throttled requests that will be made between garbage collection.
    static const uint16_t kNewEntriesBetweenCollecting;

    URLRequestBackoffManager();
    ~URLRequestBackoffManager() override;

    // Updates internal states with a response.
    void UpdateWithResponse(const GURL& url,
        HttpResponseHeaders* headers,
        const base::Time& response_time);

    // Returns whether the request should be rejected because of a Backoff header.
    bool ShouldRejectRequest(const GURL& url, const base::Time& request_time);

    // IPAddressObserver implementation.
    void OnIPAddressChanged() override;

    // ConnectionTypeObserver implementation.
    void OnConnectionTypeChanged(
        NetworkChangeNotifier::ConnectionType type) override;

    // Used by tests.
    int GetNumberOfEntriesForTests() const;

private:
    // An struct that holds relevant information obtained from a Backoff header.
    struct Entry {
        Entry(const base::Time& time1, const base::Time& time2)
            : throttled_time(time1)
            , release_time(time2)
            , used(false)
        {
        }
        ~Entry() { }

        // Returns whether this entry is outdated.
        bool IsOutDated() { return base::Time::Now() >= release_time; }

        // Before this time, requests with the same URL ID should be throttled.
        const base::Time throttled_time;

        // Only one request with the same URL ID should be allowed in
        // [|throttled_time|, |release_time|).
        // After this time, all requests with the same URL ID are allowed.
        const base::Time release_time;

        // Indicates whether a request has been made in
        // [|throttled_time|, |release_time|).
        bool used;
    };

    // From each URL, generate an ID composed of the scheme, host, port and path
    // that allows unique mapping an entry to it.
    typedef std::map<std::string, Entry*> UrlEntryMap;

    // Method that ensures the map gets cleaned from time to time. The period at
    // which garbage collecting happens is adjustable with the
    // kNewEntriesBetweenCollecting constant.
    void GarbageCollectEntriesIfNecessary();

    // Return true if there is a well-formed Backoff header key-value pair,
    // and write the Backoff header value in |result|. Return false if no header
    // is found or the value is invalid (i.e. less than kMinimumBackoffInSeconds
    // or greater than kMaximumBackoffInSeconds).
    bool GetBackoffTime(HttpResponseHeaders* headers,
        base::TimeDelta* result) const;

    // Method that transforms a URL into an ID that can be used in the map.
    // Resulting IDs will be lowercase and consist of the scheme, host, port
    // and path (without query string, fragment, etc.).
    // If the URL is invalid, the invalid spec will be returned, without any
    // transformation.
    std::string GetIdFromUrl(const GURL& url) const;

    // When switching from online to offline or change IP addresses,
    // clear all back-off history. This is a precaution in case the change in
    // online state now allows communicating without errors with servers that
    // were previously returning Backoff headers.
    void OnNetworkChange();

    UrlEntryMap url_entries_;

    // Keeps track of how many new entries are created since last garbage
    // collection.
    unsigned int new_entries_since_last_gc_;

    // Valid after construction.
    GURL::Replacements url_id_replacements_;

    DISALLOW_COPY_AND_ASSIGN(URLRequestBackoffManager);
};

} // namespace net

#endif // NET_URL_REQUEST_URL_REQUEST_BACKOFF_MANAGER_H_
